
import java.io.*;
import java.util.ArrayList;
import java.util.BitSet;

/*
 ********************************************************************************** 
  Classe que inicará a execução das instruções e gerenciará o processo
 **********************************************************************************
 */
public class cpu {
  public static final int MD_DIRETO = 0;
  public static final int MD_PASSO_PASSO = 1;
  
  private ArrayList<registrador> bancoReg;
  private alu al;
  private memoriaPrinc memDados;
  private controle c;
  private memoriaInst mem; 
  
  //cria o banco de registradores e instancia cada um deles
  public cpu(){
    bancoReg = new ArrayList(32);
    memDados = new memoriaPrinc();
    c =  new controle();
    mem = new memoriaInst();
    
    //Instancia o registrador $zero
    registrador registrador = new registrador(0,"$zero");
    bancoReg.add(registrador);
    
    //Instancia o registrador $at
    registrador = new registrador(1,"$at");
    bancoReg.add(registrador);
    
    int contaReg = 2; //Marca o número do próximo registrador na memória
    
    //Inicializa registradores v0 e v1
    for (int i=0;i<=1;i++){
      registrador  = new registrador(contaReg, "$v" + i);
      bancoReg.add(registrador);
      contaReg++;
    }
    //Inicializa registradores a0 a a3
    for (int i=0;i<=3;i++){
      registrador  = new registrador(contaReg, "$a" + i);
      bancoReg.add(registrador);
      contaReg++;
    }
    //Inicializa registradores t0 a t7
    for (int i=0;i<=7;i++){
      registrador  = new registrador(contaReg, "$t" + i);
      bancoReg.add(registrador);
      contaReg++;
    }
    //Inicializa registradores s0 a s7
    for (int i=0;i<=7;i++){
      registrador  = new registrador(contaReg, "$s" + i);
      bancoReg.add(registrador);
      contaReg++;
    }
    //Inicializa registradores t8 e t9
    for (int i=8;i<=9;i++){
      registrador  = new registrador(contaReg, "$t" + i);
      bancoReg.add(registrador);
      contaReg++;
    }
    //Inicializa registradores k0 e k1
    for (int i=0;i<=1;i++){
      registrador  = new registrador(contaReg, "$k" + i);
      bancoReg.add(registrador);
      contaReg++;
    }
    
    //Instancia o registrador $gp
    registrador = new registrador(contaReg,"$gp");
    bancoReg.add(registrador);
    contaReg++;
    
    //Instancia o registrador $sp
    registrador = new registrador(contaReg,"$sp");
    bancoReg.add(registrador);
    contaReg++;
    
    //Instancia o registrador $sp
    registrador = new registrador(contaReg,"$fp");
    bancoReg.add(registrador);
    contaReg++;
    
    //Instancia o registrador $sp
    registrador = new registrador(contaReg,"$ra");
    bancoReg.add(registrador);
    
    al = new alu();
  }
  
  //Grava um valor no registrador contido na posição especificada
  public void gravaReg(int index, int valor){
    registrador reg = bancoReg.get(index);
    reg.setValor(valor);
  }
  
  public int getValorReg(int index){
    registrador reg = bancoReg.get(index);
    return reg.getValor();
  }
  

   
  public boolean executa(int modo){
    boolean retorno = false;
    switch (modo){
      case 0:{
        executaDireto();
        break; 
      }
      case 1:{
        retorno = executaPassoPasso();
        break;
      }  
    }
    return retorno;
  }
  
  private void executaDireto(){
    instrucao i;
    escreveLog();
    i = mem.getInst();
    do{
      processaInst(i);
      escreveLog();
      i = mem.getInst();
    } while(i != null);
    
  }
  
  private boolean executaPassoPasso(){
    instrucao i;
    i = mem.getInst();
    if (i != null){
      processaInst(i);
      escreveLog();
      return true;
    }
    return false;
  }
  
  private void processaInst(instrucao i){
    int opCode = i.getOpCode();
      if (opCode == instrucaoR.OPCODE_R){
        instrucaoR inst = (instrucaoR)i;
        c.setSinais(inst);
        if (c.getJump() == 0){
          int result = execInstR(inst);
          gravaReg(inst.getrD().getEndereco(), result);
        }else{
          int novoPC = getValorReg(inst.getrS().getEndereco());
          mem.setPc(novoPC);
        }  
      }else if((opCode == instrucaoJ.OPCODE_J) || (opCode == instrucaoJ.OPCODE_JAL)){
        execInstJ((instrucaoJ)i);
      }else{
        instrucaoI inst = (instrucaoI)i;
        int result = execInstI(inst);
        if (c.getBranch() == 1) {
          if((result == 1) && (c.getInverteZero() == 0)){
            int endDesvio = al.somador(mem.getPc(), inst.getImediato());
            mem.setPc(endDesvio);
          }else if((result ==0) && (c.getInverteZero() == 1)){
            int endDesvio = al.somador(mem.getPc(), inst.getImediato());
            mem.setPc(endDesvio);
          }
        }else{
          if ((c.getMemParaReg() == 1) || (c.getEscreveMem() != 1)){
              gravaReg(inst.getrT().getEndereco(), result);     
          }else{
            registrador rT = bancoReg.get(inst.getrT().getEndereco());
            memDados.grava(result, rT.getValor());
          }  
        }  
      }
  }
  
  private int execInstR(instrucaoR i){
    int op2;
    registrador rs =  bancoReg.get(i.getrS().getEndereco());
    registrador rt =  bancoReg.get(i.getrT().getEndereco());
    int op1 = rs.getValor();
    if (c.getOrigAlu() == 0){
      op2 = rt.getValor();
    }else{
      op2 = i.getShmt();
    }  
    al.opera(op1, op2, c.getControleAlu());
    return al.getResultado();
  }

  private void execInstJ(instrucaoJ i) {
    c.setSinais(i);
    if (c.getSalvaPC() == 1){
      registrador r = bancoReg.get(31);
      r.setValor(mem.getPc());
    }
    mem.setPc(i.getEndJump());
  }

  private int execInstI(instrucaoI i) {
    c.setSinais(i);
    registrador rs =  bancoReg.get(i.getrS().getEndereco());
    registrador rt = bancoReg.get(i.getrT().getEndereco());
    int op1;
    int op2;
     
    if (c.getOrigAlu() == 1){
      op1 = rs.getEndereco();
    }else{
      op1 = rs.getValor();
    }
    if (c.getBranch() != 1){
      op2 = i.getImediato();
    }else{
      op2 = rt.getValor();
    }  
    al.opera(op1, op2, c.getControleAlu());
    if (c.getMemParaReg() == 1){
      int endereco = al.getResultado();
      return memDados.get(endereco);
    }else if (c.getBranch() != 1){
      return al.getResultado();  
    }else{
      return al.getZero();
    }
      
    
    
  }
  
  //carrega as instruções na memória(chamando a função gravaInst) de acordo com o arraylist preenchido com a função anterior
  public void leInstrucoes(ArrayList lista){
    for (int i = 0;i < lista.size();i++){
      String num = lista.get(i).toString();
      BitSet b = new BitSet(32);
      for (int j=num.length()-1;j>=0;j--){
        b.set(31-j, num.charAt(j) == '1');  
      }    
      gravaInst(b);
    }
  }
  
  //Reconhece qual o tipo da instrução através do opCode, instancia o objeto e o adiciona na memória
  private void gravaInst(BitSet inst){
    BitSet opCode;
    opCode = inst.get(26, 32);
    int opCodeInt = Integer.parseInt(funcoes.bitSetStr(opCode,6, true),2);  
    if (opCodeInt == instrucaoR.OPCODE_R) {
      instrucaoR instrucao  = new instrucaoR(inst);
      mem.add(instrucao);          
    }else if((opCodeInt == instrucaoJ.OPCODE_J) || (opCodeInt == instrucaoJ.OPCODE_JAL)){
      instrucaoJ instrucao  = new instrucaoJ(inst);
      mem.add(instrucao);
    }else{
      instrucaoI instrucao  = new instrucaoI(inst);
      mem.add(instrucao);
    }            
  }
  
  public ArrayList<String> getInstNames(){
    return mem.getInstNames();
  }
  
  public String getCurrInst(){
    return mem.getNomeInst();
  }
  
  public String[] getValorRegs(){
    String[] a = new String[32];
    for (int i = 0; i < bancoReg.size(); i++) {
      a[i] = bancoReg.get(i).getValor()+"";
    }
    return a;
  }
  
  public int getPc(){
    return mem.getPc();
  }
  
  public String getDados(){
    return memDados.toString();
  }
  
  private void escreveLog(){
    try{
      BufferedReader reader = new BufferedReader(new FileReader("log.txt"));
      String linha;
      String texto = "";
      while((linha = reader.readLine()) != null){  
        texto += linha + '\n';  
      }
      texto += '\n';
      texto += "PC: " + mem.getPc() + " - Próxima Instrucao: " +getCurrInst() + '\n';
      for (int i=0;i<bancoReg.size();i++){
        registrador reg = bancoReg.get(i);
        texto += "  " + reg.toString() + '\n';
      }
      texto += '\n';
      texto += "Memoria de dados" + '\n';
      String[] a = memDados.getDados();
      for (int i=0;i<a.length;i++){
        texto += "  Posicao " + i + ": " + a[i] +'\n';
      }
      
      reader.close();
      BufferedWriter br = new BufferedWriter(new FileWriter(new File("log.txt")));
      br.write(texto);
      br.close();
    }catch(Exception e){
      funcoes.imprime("Não foi possível salvar o arquivo de log");
    }
  }
}
